// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/display/chromeos/x11/native_display_event_dispatcher_x11.h"

#include <X11/extensions/Xrandr.h>
#include <utility>

#include "base/time/default_tick_clock.h"
#include "ui/display/chromeos/x11/display_mode_x11.h"
#include "ui/display/chromeos/x11/display_snapshot_x11.h"
#include "ui/events/platform/platform_event_source.h"

namespace ui {

// static
const int NativeDisplayEventDispatcherX11::kUseCacheAfterStartupMs = 7000;

NativeDisplayEventDispatcherX11::NativeDisplayEventDispatcherX11(
    NativeDisplayDelegateX11::HelperDelegate* delegate,
    int xrandr_event_base)
    : delegate_(delegate)
    , xrandr_event_base_(xrandr_event_base)
    , tick_clock_(new base::DefaultTickClock)
{
    startup_time_ = tick_clock_->NowTicks();
}

NativeDisplayEventDispatcherX11::~NativeDisplayEventDispatcherX11() { }

bool NativeDisplayEventDispatcherX11::CanDispatchEvent(
    const PlatformEvent& event)
{
    return (event->type - xrandr_event_base_ == RRScreenChangeNotify) || (event->type - xrandr_event_base_ == RRNotify);
}

uint32_t NativeDisplayEventDispatcherX11::DispatchEvent(
    const PlatformEvent& event)
{
    if (event->type - xrandr_event_base_ == RRScreenChangeNotify) {
        VLOG(1) << "Received RRScreenChangeNotify event";
        delegate_->UpdateXRandRConfiguration(event);
        return ui::POST_DISPATCH_PERFORM_DEFAULT;
    }

    // Bail out early for everything except RRNotify_OutputChange events
    // about an output getting connected or disconnected.
    if (event->type - xrandr_event_base_ != RRNotify)
        return ui::POST_DISPATCH_PERFORM_DEFAULT;
    const XRRNotifyEvent* notify_event = reinterpret_cast<XRRNotifyEvent*>(event);
    if (notify_event->subtype != RRNotify_OutputChange)
        return ui::POST_DISPATCH_PERFORM_DEFAULT;
    const XRROutputChangeNotifyEvent* output_change_event = reinterpret_cast<XRROutputChangeNotifyEvent*>(event);
    const int action = output_change_event->connection;
    if (action != RR_Connected && action != RR_Disconnected)
        return ui::POST_DISPATCH_PERFORM_DEFAULT;

    const bool connected = (action == RR_Connected);
    VLOG(1) << "Received RRNotify_OutputChange event:"
            << " output=" << output_change_event->output
            << " crtc=" << output_change_event->crtc
            << " mode=" << output_change_event->mode
            << " action=" << (connected ? "connected" : "disconnected");

    bool check_cache = (tick_clock_->NowTicks() - startup_time_)
                           .InMilliseconds()
        <= kUseCacheAfterStartupMs;

    if (check_cache) {
        bool found_changed_output = false;
        const std::vector<DisplaySnapshot*>& cached_outputs = delegate_->GetCachedDisplays();
        for (std::vector<DisplaySnapshot*>::const_iterator it = cached_outputs.begin();
             it != cached_outputs.end();
             ++it) {
            const DisplaySnapshotX11* x11_output = static_cast<const DisplaySnapshotX11*>(*it);
            const DisplayModeX11* x11_mode = static_cast<const DisplayModeX11*>(x11_output->current_mode());
            RRMode mode_id = x11_mode ? x11_mode->mode_id() : None;

            // Update if we failed to fetch the external display's ID before.
            // Internal display's EDID should always be available.
            bool display_id_needs_update = x11_output->type() != ui::DISPLAY_CONNECTION_TYPE_INTERNAL && !x11_output->display_id();

            if (x11_output->output() == output_change_event->output) {
                if (connected && x11_output->crtc() == output_change_event->crtc && mode_id == output_change_event->mode && !display_id_needs_update) {
                    VLOG(1) << "Ignoring event describing already-cached state";
                    return POST_DISPATCH_PERFORM_DEFAULT;
                }
                found_changed_output = true;
                break;
            }
        }

        if (!connected && !found_changed_output) {
            VLOG(1) << "Ignoring event describing already-disconnected output";
            return ui::POST_DISPATCH_PERFORM_DEFAULT;
        }
    }

    delegate_->NotifyDisplayObservers();

    return ui::POST_DISPATCH_PERFORM_DEFAULT;
}

void NativeDisplayEventDispatcherX11::SetTickClockForTest(
    scoped_ptr<base::TickClock> tick_clock)
{
    tick_clock_ = std::move(tick_clock);
    startup_time_ = tick_clock_->NowTicks();
}

} // namespace ui
